using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using Hive.Core.Common.Collections;
using Hive.Core.Common.IO;
using Hive.Core.Common.Shell;

namespace Hive.Core.Model
{
    /// <summary>
    /// Default implementation of a package branch.
    /// </summary>
    public class DefaultPackageBranch : IPackageBranch
    {
        private const string UsesSourceKey = "UsesSource";

        private readonly DefaultPackage package;
        private readonly DirectoryInfo directory;

        /// <summary>
        /// Create a package branch.
        /// </summary>
        /// <param name="package">The package.</param>
        /// <param name="directory">The package branch directory.</param>
        /// <exception cref="ArgumentNullException">Thrown if <paramref name="package"/> or <paramref name="directory"/> is null.</exception>
        public DefaultPackageBranch(DefaultPackage package, DirectoryInfo directory)
        {
            if (package == null)
                throw new ArgumentNullException("package");
            if (directory == null)
                throw new ArgumentNullException("directory");

            this.package = package;
            this.directory = directory;
        }

        /// <inheritdoc />
        public IHive Hive
        {
            get { return package.Hive; }
        }

        /// <inheritdoc />
        public IPackage Package
        {
            get { return package; }
        }

        /// <inheritdoc />
        public string Name
        {
            get { return directory.Name; }
        }

        /// <inheritdoc />
        public DirectoryInfo Directory
        {
            get { return directory; }
        }

        /// <inheritdoc />
        public DirectoryInfo GetContentsDirectory()
        {
            return new DirectoryInfo(Path.Combine(directory.FullName, "contents"));
        }

        /// <inheritdoc />
        public DirectoryInfo GetImportsDirectory()
        {
            return new DirectoryInfo(Path.Combine(directory.FullName, "imports"));
        }

        /// <inheritdoc />
        public DirectoryInfo GetExportsDirectory()
        {
            return new DirectoryInfo(Path.Combine(directory.FullName, "exports"));
        }

        /// <inheritdoc />
        public bool IsPresent()
        {
            try
            {
                FileInfo packageDescriptorFile = GetPackageDescriptorFile();
                return packageDescriptorFile.Exists;
            }
            catch
            {
                return false;
            }
        }

        /// <inheritdoc />
        public bool IsUsingSource()
        {
            try
            {
                Descriptor packageDescriptor = LoadPackageDescriptor();
                return packageDescriptor.GetProperty(UsesSourceKey) == bool.TrueString;
            }
            catch (Exception ex)
            {
                throw new HiveException("An exception occurred while getting the package uses source flag.", ex);
            }
        }

        /// <inheritdoc />
        public void SetUsingSource(bool value)
        {
            try
            {
                Descriptor packageDescriptor = LoadPackageDescriptor();
                bool usesSource = packageDescriptor.GetProperty(UsesSourceKey) == bool.TrueString;

                if (value == usesSource)
                    return;

                packageDescriptor.SetProperty(UsesSourceKey, value.ToString());
                SavePackageDescriptor(packageDescriptor);
            }
            catch (Exception ex)
            {
                throw new HiveException("An exception occurred while setting the package uses source flag.", ex);
            }
        }

        /// <inheritdoc />
        public void GetSource(IShell shell)
        {
            if (shell == null)
                throw new ArgumentNullException("shell");

            try
            {
                shell.Echo(MessageSeverity.Important, string.Format("Getting source for branch '{0}' of package '{1}'...", package.Name, Name));

                SetUsingSource(true);

                DirectoryInfo contentsDirectory = GetContentsDirectory();
                Hive.SourceRepository.CheckoutOrUpdate(Package.Name, Name, contentsDirectory, shell);

                shell.Echo(MessageSeverity.Important, "\nImporting dependencies...");

                DirectoryInfo importsDirectory = GetImportsDirectory();
                if (!importsDirectory.Exists)
                    importsDirectory.Create();

                foreach (IPackageBranch dependency in GetDependencies())
                {
                    DirectoryInfo dependencyExportsDirectory = dependency.GetExportsDirectory();
                    if (!dependencyExportsDirectory.Exists)
                        dependencyExportsDirectory.Create();

                    string linkPath = Path.Combine(importsDirectory.FullName, dependency.Package.Name);
                    JunctionPoint.Create(linkPath, dependencyExportsDirectory.FullName, true);
                }

                shell.Echo(MessageSeverity.Important, "Done.\n");
            }
            catch (HiveException)
            {
                throw;
            }
            catch (Exception ex)
            {
                throw new HiveException("An exception occurred while getting source for a package branch.", ex);
            }
        }

        /// <inheritdoc />
        public void GetBinary(string versionLabel, IShell shell)
        {
            if (shell == null)
                throw new ArgumentNullException("shell");

            try
            {
                shell.Echo(MessageSeverity.Important, string.Format("Getting binary for branch '{0}' of package '{1}'...", package.Name, Name));

                SetUsingSource(false);

                IBinaryArtifact artifact;
                if (versionLabel == null)
                {
                    IList<IBinaryArtifact> artifacts = Hive.BinaryRepository.GetArtifacts(Package.Name, Name, shell);
                    if (artifacts.Count == 0)
                        throw new HiveException("No binary artifacts are available!");

                    artifact = artifacts.OrderByDescending(x => x.CreationTimeUtc).Single();
                }
                else
                {
                    artifact = Hive.BinaryRepository.GetArtifact(Package.Name, Name, versionLabel, shell);
                }

                DirectoryInfo exportsDirectory = RecreateExportsDirectory();
                artifact.Extract(exportsDirectory, shell);

                shell.Echo(MessageSeverity.Important, "\nDone.");
            }
            catch (HiveException)
            {
                throw;
            }
            catch (Exception ex)
            {
                throw new HiveException("An exception occurred while getting binary for a packag branch.", ex);
            }
        }

        /// <inheritdoc />
        public void Build(IShell shell)
        {
            if (shell == null)
                throw new ArgumentNullException("shell");

            try
            {
                shell.Echo(MessageSeverity.Important, string.Format("Building branch '{0}' of package '{1}'...", package.Name, Name));

                FileInfo buildScriptFile = GetBuildScriptFile();
                if (!buildScriptFile.Exists)
                    throw new HiveException(string.Format("Build failed because there is no build script file in path '{0}'.", buildScriptFile.FullName));

                DirectoryInfo importsDirectory = GetImportsDirectory();
                DirectoryInfo exportsDirectory = RecreateExportsDirectory();
                DirectoryInfo contentsDirectory = GetContentsDirectory();

                var shellCommand = new ShellCommand(buildScriptFile.FullName,
                    string.Concat("\"", importsDirectory.FullName, "\" \"", exportsDirectory.FullName, "\""),
                    contentsDirectory)
                {
                    Cancelable = true
                };

                int exitCode = shell.Execute(shellCommand, null, null);
                if (exitCode != 0)
                    throw new HiveException(string.Format("Build script exited with code {0}.", exitCode));

                shell.Echo(MessageSeverity.Important, "Done.\n");
            }
            catch (HiveException)
            {
                throw;
            }
            catch (Exception ex)
            {
                throw new HiveException("An exception occured while building a package branch.", ex);
            }
        }

        private FileInfo GetBuildScriptFile()
        {
            return new FileInfo(Path.Combine(GetContentsDirectory().FullName, "PackageBuild.bat"));
        }

        /// <inheritdoc />
        public IList<IPackageBranch> GetDependencies()
        {
            try
            {
                if (IsUsingSource())
                {
                    FileInfo dependenciesFile = GetDependenciesFile();
                    if (dependenciesFile.Exists)
                    {
                        var dependencies = new List<IPackageBranch>();

                        using (TextReader reader = dependenciesFile.OpenText())
                        {
                            for (; ; )
                            {
                                string line = reader.ReadLine();
                                if (line == null)
                                    break;

                                line = line.Trim();
                                if (!line.StartsWith("#") && line.Length != 0)
                                {
                                    var dependency = Hive.GetPackage(line).GetBranch(Name);
                                    dependencies.Add(dependency);
                                }
                            }
                        }

                        return dependencies;
                    }
                }
            }
            catch (HiveException)
            {
                throw;
            }
            catch (Exception ex)
            {
                throw new HiveException("An exception occurred while getting package branch dependencies.", ex);
            }

            return EmptyArray<IPackageBranch>.Instance;
        }

        private FileInfo GetDependenciesFile()
        {
            return new FileInfo(Path.Combine(GetContentsDirectory().FullName, "PackageDependencies.txt"));
        }

        private DirectoryInfo RecreateExportsDirectory()
        {
            DirectoryInfo exportsDirectory = GetExportsDirectory();
            try
            {
                if (exportsDirectory.Exists)
                    exportsDirectory.Delete(true);
                exportsDirectory.Create();
                return exportsDirectory;
            }
            catch (Exception ex)
            {
                throw new HiveException(string.Format("Could not recreate directory '{0}'.", exportsDirectory.FullName), ex);
            }
        }

        private Descriptor LoadPackageDescriptor()
        {
            FileInfo packageDescriptorFile = GetPackageDescriptorFile();
            if (packageDescriptorFile.Exists)
                return Descriptor.Load(packageDescriptorFile);
            return new Descriptor();
        }

        private void SavePackageDescriptor(Descriptor packageDescriptor)
        {
            FileInfo packageDescriptorFile = GetPackageDescriptorFile();
            if (!packageDescriptorFile.Directory.Exists)
                packageDescriptorFile.Directory.Create();

            packageDescriptor.Save(packageDescriptorFile);
        }

        private FileInfo GetPackageDescriptorFile()
        {
            return new FileInfo(Path.Combine(directory.FullName, ".package"));
        }
    }
}